/*   Console.js ver 0.1.1
 * 
 * Components.utils.import("resource://package/Console.js")
 *   Console.log("log message"); // "log message" in error console
 * You can also generate console instance, in which you can set log level, prefix etc.
 *   var console = new Console({level: "warn", prefix: "[debug] "});
 *   console.log("log message"); // nothing happen
 *   console.error("warning message"); // "[debug] warning message" in error console
 * Console constructor parameters:
 *   level:     log level (see below const definition)
 *   dumplevel: log level for dump message
 *   prefix:    log message prefix
 *   target:    "console", "firebug" or "both" ("both" by default)
 */

var EXPORTED_SYMBOLS = ["Console"];

const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
const Cu = Components.utils;

const level = {
	all: 0,
	log: 10,
	debug: 20,
	info: 30,
	warn: 40,
	error: 50,
	assert: 60,
	off: 100,
	// others are same level as info
	dir: 30,
	dirxml: 30,
	trace: 30,
	time: 30,
	count: 30,
	exception: 30
}

function log(message) {
	Lib.nsIConsoleService.logStringMessage(message);
}

var Lib = {
	// https://developer.mozilla.org/en/NsIConsoleService
	get nsIConsoleService() {
		delete this.nsIConsoleService;
		return this.nsIConsoleService = Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService);
	},
	get scripterror() {
		delete this.scripterror;
		return this.scripterror = Cc["@mozilla.org/scripterror;1"];
	},
	get nsIScriptError() {
		delete this.nsIScriptError;
		return this.nsIScriptError = Ci.nsIScriptError;
	},
	get nsIPromptService() {
		delete this.nsIPromptService;
		return this.nsIPromptService = Cc["@mozilla.org/embedcomp/prompt-service;1"].getService(Ci.nsIPromptService);
	},
	// use windowMediator to get Firebug objects from main window
	get nsIWindowMediator() {
		delete this.nsIWindowMediator;
		return this.nsIWindowMediator = Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);
	},
	get mainWindow() this.nsIWindowMediator.getMostRecentWindow("navigator:browser"),
	get firebugConsole() {
		/* 
		 * Firebug を一度閉じると FirebugContext などは削除され次回呼び出し時は再生成される
		 * しかしその時に wrappedJSObject.console は古いのが残ったままで出力先の対応が壊れる
		 * 従ってデバッグ中に一度閉じるとその後エラー出力が一切されなくなる問題がある...
		 * Firebug 側のバグまたは仕様であるが何とかしたいところ...
		 */
		var mainWindow = this.mainWindow;
		var Firebug = mainWindow.Firebug;
		if (!Firebug) { return; }
		if (!mainWindow.FirebugContext || !Firebug.Console.isEnabled()) {
			//Firebug.onClickStatusIcon(mainWindow.FirebugContext, {button:0});
			Firebug.toggleBar(false, 'console');
		}
		var FirebugContext = mainWindow.FirebugContext;
		if (!FirebugContext) { return; }
		//log("FirebugContext is't available");
		//var wrappedJSObject = mainWindow.gBrowser.contentWindow.wrappedJSObject;
		var wrappedJSObject = FirebugContext.global.wrappedJSObject;
		if (wrappedJSObject && wrappedJSObject.console)
			return wrappedJSObject.console;
		//log("wrappedJSObject.console isn't available");
		//if (FirebugContext.global.console)
		//	return FirebugContext.global.console;
		//Firebug.Console.injector.attachIfNeeded(FirebugContext, FirebugContext.window);
		Firebug.Console.injector.attachIfNeeded(FirebugContext, FirebugContext.global);
		var wrappedJSObject = FirebugContext.global.wrappedJSObject;
		if (wrappedJSObject && wrappedJSObject.console)
			return wrappedJSObject.console;
		log("sorry couldn't get firebug console...");
	},
	get serializer() {
		delete this.serializer;
		return this.serializer = new XMLSerializer();
	},
	consoleLog: function debug_consoleLog(message) {
		Lib.nsIConsoleService.logStringMessage(message);
	},
	consoleWarn: function debug_consoleWarn(message) {
		var stack = Components.stack.caller.caller;
		var error = Lib.scripterror.createInstance(Lib.nsIScriptError);
		error.init(message, stack.filename, null, stack.lineNumber, null, Lib.nsIScriptError.warningFlag, null);
		Lib.nsIConsoleService.logMessage(error);
	},
	consoleError: function debug_consoleError(message) {
		var stack = Components.stack.caller.caller;
		var error = Lib.scripterror.createInstance(Lib.nsIScriptError);
		error.init(message, stack.filename, null, stack.lineNumber, null, Lib.nsIScriptError.errorFlag, null);
		Lib.nsIConsoleService.logMessage(error);
	},
	throwError: function debug_throw(message) {
		var stack = Components.stack.caller.caller;
		var error = Lib.scripterror.createInstance(Lib.nsIScriptError);
		error.init(message, stack.filename, null, stack.lineNumber, null, Lib.nsIScriptError.errorFlag, null);
		throw error;
	},
	trace: function debug_trace() {
		var stack = Components.stack.caller.caller;
		var error = Lib.scripterror.createInstance(Lib.nsIScriptError);
		error.init(message, stack.filename, null, stack.lineNumber, null, Lib.nsIScriptError.errorFlag, null);
		Lib.nsIConsoleService.logMessage(error);
	},
	alert: function debug_alert(message, title) {
		Lib.nsIPromptService.alert(null, title, message);
	}
}

function Console(param) {
	if (param) {
		if (typeof param.level === "string" && level[param.level] >= 0)
			this.level = level[param.level];
		if (typeof param.dumplevel === "string" && level[param.dumplevel] >= 0)
			this.dumplevel = level[param.dumplevel];
		if (typeof param.prefix == "string")
			this.prefix = param.prefix;
	}
	this._time = {};
	this._count = {};
}

// Extended Firebug Console API with log level support
// fallback to console etc if Firebug is not available
Console.prototype = {
	// default config
	level: 0,     // output all by default
	dumplevel: 0, // dump all by default
	prefix: "",
	assertErrorTitle: "Assertion Failed!",
	_time: {},
	_count: {},
	// Firebug Console API:
	// http://getfirebug.com/wiki/index.php/Console_API
	log: function(message) {
		if (this.level <= level.log) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.log.apply(firebugConsole, arguments);
			else
				Lib.consoleLog(this.prefix+message);
		}
		if (this.dumplevel <= level.log) {
			dump("[log] "+this.prefix+message+"\n");
		}
	},
	debug: function(message) {
		if (this.level <= level.debug) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.debug.apply(firebugConsole, arguments);
			else
				Lib.consoleLog(this.prefix+message);
		}
		if (this.dumplevel <= level.debug) {
			dump("[debug] "+this.prefix+message+"\n");
		}
	},
	info: function(message) {
		if (this.level <= level.info) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.info.apply(firebugConsole, arguments);
			else
				Lib.consoleLog(this.prefix+message);
		}
		if (this.dumplevel <= level.info) {
			dump("[info] "+this.prefix+message+"\n");
		}
	},
	warn: function(message) {
		if (this.level <= level.warn) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.warn.apply(firebugConsole, arguments);
			else
				Lib.consoleWarn(this.prefix+message);
		}
		if (this.dumplevel <= level.warn) {
			dump("[warn] "+this.prefix+message+"\n");
		}
	},
	error: function(message) {
		if (this.level <= level.error) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.error.apply(firebugConsole, arguments);
			else
				Lib.consoleError(this.prefix+message);
		}
		if (this.dumplevel <= level.error) {
			dump("[error] "+this.prefix+message+"\n");
		}
	},
	assert: function(cond, message) {
		if (this.level <= level.assert && !cond) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.assert.apply(firebugConsole, arguments);
			else {
				message = this.prefix+message;
				Lib.consoleError(message);
				//Lib.alert(null, this.assertErrorTitle, message);
				Lib.throwError(message);
			}
		}
		if (this.dumplevel <= level.assert && !cond) {
			dump("[assertion failed] "+this.prefix+message+"\n");
		}
		// return !!cond;
	},
	dir: function(object) {
		if (this.level <= level.dir) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.dir.apply(firebugConsole, arguments);
			else
				Lib.consoleLog(this.prefix+object.toSource());
		}
		if (this.dumplevel <= level.dir) {
			dump("[dir] "+this.prefix+object.toSource()+"\n");
		}
	},
	dirxml: function(node) {
		if (this.level <= level.dirxml) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.dirxml.apply(firebugConsole, arguments);
			else
				Lib.consoleLog(this.prefix+Lib.serializer.serializeToString(node));
		}
		if (this.dumplevel <= level.dirxml) {
			dump("[dirxml] "+this.prefix+Lib.serializer.serializeToString(node)+"\n");
		}
	},
	trace: function() {
		if (this.level <= level.trace) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.trace();
			else {
				var error = new Error();
				var callerstack = error.stack.replace(/^.*\n.*\n/, "");
				Lib.consoleLog(this.prefix+callerstack);
			}
		}
		if (this.dumplevel <= level.trace) {
			var error = new Error();
			var callerstack = error.stack.replace(/^.*\n.*\n/, "");
			dump("[trace] "+this.prefix+callerstack+"\n");
		}
	},
	group: function() {
		var firebugConsole = Lib.firebugConsole;
		if (firebugConsole)
			firebugConsole.group.apply(firebugConsole, arguments);
		else {
			Lib.consoleLog("Firebug is required to use Console.group method.");
			dump("[group] Firebug is required to use Console.group method.");
		}
	},
	groupCollapsed: function() {
		var firebugConsole = Lib.firebugConsole;
		if (firebugConsole)
			firebugConsole.groupCollapsed.apply(firebugConsole, arguments);
		else {
			Lib.consoleLog("Firebug is required to use Console.groupCollapsed method.");
			dump("[groupCollapsed] Firebug is required to use Console.groupCollapsed method.");
		}
	},
	groupEnd: function() {
		var firebugConsole = Lib.firebugConsole;
		if (firebugConsole)
			firebugConsole.groupEnd.apply(firebugConsole, arguments);
		else {
			Lib.consoleLog("Firebug is required to use Console.groupEnd method.");
			dump("[groupEnd] Firebug is required to use Console.groupEnd method.");
		}
	},
	time: function(name) {
		if (this.level <= level.time) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.time(name);
			else if (!this._time[name])
				this._time[name] = Date.now();
		}
	},
	timeEnd: function(name) {
		if (this.level <= level.time) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.timeEnd(name);
			else if (this._time[name]) {
				Lib.consoleLog(this.prefix+name+": "+Date.now()-this._time[name]+"ms");
				delete this._time[name];
			}
		}
		if (this.dumplevel <= level.time) {
			dump("[time] "+this.prefix+name+": "+Date.now()-this._time[name]+"ms\n");
		}
	},
	profile: function(title) {
		var firebugConsole = Lib.firebugConsole;
		if (firebugConsole)
			firebugConsole.profile(title);
		else {
			Lib.consoleLog("Firebug is required to use Console.profile method.");
			dump("[profile] Firebug is required to use Console.profile method.");
		}
	},
	profileEnd: function() {
		var firebugConsole = Lib.firebugConsole;
		if (firebugConsole)
			firebugConsole.profileEnd();
		else {
			Lib.consoleLog("Firebug is required to use Console.profileEnd method.");
			dump("[profileEnd] Firebug is required to use Console.profileEnd method.");
		}
	},
	count: function(title) {
		if (this.level <= level.count) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.count(title);
			else {
				this._count[title] = this._count[title] ? 1 : this._count[title]+1;
				Lib.consoleLog(this.prefix+title+": "+this._count[title]);
			}
		}
		if (this.dumplevel <= level.count) {
			dump("[count] "+this.prefix+title+": "+this._count[title]+"\n");
		}
	},
	exception: function(error) {
		if (this.level <= level.exception) {
			var firebugConsole = Lib.firebugConsole;
			if (firebugConsole)
				firebugConsole.exception.apply(firebugConsole, arguments);
			else
				Cu.reportError(error);
		}
	}
}

// make users can use Console without creating an instance first.
Console.__proto__ = Console.prototype;

function isArray(v) v && typeof v=="object" && v.constructor.name=="Array";
function isObject(v) v && typeof v=="object" && v.constructor.name=="Object";
